package tools.gff;

import java.util.ArrayList;
import java.util.Iterator;

public class gffLine {

//	   1. seqname - The name of the sequence. Must be a chromosome or scaffold.
//	   2. source - The program that generated this feature.
//	   3. feature - The name of this type of feature. Some examples of standard feature types are "CDS", "start_codon", "stop_codon", and "exon".
//	   4. start - The starting position of the feature in the sequence. The first base is numbered 1.
//	   5. end - The ending position of the feature (INCLUSIVE).
//	   6. score - A score between 0 and 1000. If the track line useScore attribute is set to 1 for this annotation data set, the score value will determine the level of gray in which this feature is displayed (higher numbers = darker gray). If there is no score value, enter ".".
//	   7. strand - Valid entries include '+', '-', or '.' (for don't know/don't care).
//	   8. frame - If the feature is a coding exon, frame should be a number between 0-2 that represents the reading frame of the first base. If the feature is not a coding exon, the value should be '.'.
//	   9. attribute - All lines with the same group are linked together into a single item. 
	
	private String chr,source,feature,score,attribute,flag,ID,Name;
	private ArrayList<String> Parent,Derives_from;
	private int start,end;
	private char strand,frame;
	private boolean hasParent,hasID,hasDerived_from,hasName;
	
	public gffLine(String line)throws Exception{
		String l[] =line.split("\t");
		this.setChr(l[0]);
		this.setSource(l[1]);
		this.setFeature(l[2]);
		this.setStart(Integer.parseInt(l[3]));
		this.setEnd(Integer.parseInt(l[4]));
		this.setScore(l[5]);
		if(l[6].length()>1){
			throw new Exception("to long strand column in :" +line);
		}
		this.setStrand(l[6].charAt(0));
		if(l[7].length()>1){
			throw new Exception("to long frame column in :" +line);
		}
		this.setFrame(l[7].charAt(0));
		this.setAttribute(l[8]);
		this.parseAttributes();
	}
	
	public gffLine(String chr,String source,String feature, int start,int end, String score,char strand,char frame,String attribute)throws Exception{
		this.setChr(chr);
		this.setSource(source);
		this.setFeature(feature);
		this.setStart(start);
		this.setEnd(end);
		this.setScore(score);
		this.setStrand(strand);
		this.setFrame(frame);
		this.setAttribute(attribute);
		this.parseAttributes();
	}
	
	public gffLine() throws Exception{
		this("", "", "", 1, 1, ".", '.', '.', "");
	}
	
	public boolean overlap(String tname,char strand,int start,int end){
		if(tname.equals(this.getChr())&&this.getStrand()==strand){
			return overlap(start,end);
		}else{
			return false;
		}
	}
	
	public boolean overlap(int start, int end){
		return (start>=this.getStart() && start<this.getEnd()) || (this.getStart()>=start && this.getStart()<end);
	}
	
	private void parseAttributes()throws Exception{
		hasParent=false;
		hasID=false;
		hasDerived_from=false;
		hasName=false;
		String[] attributes=getAttribute().split(";");
		for (String curAttribute : attributes) {
			String[] pair= curAttribute.split("=");
			if(pair[0].equals("Parent")){
				Parent= new ArrayList<String>();
				for (String s : pair[1].split(",")) {
					Parent.add(s);
				}
				hasParent=true;
			}else if(pair[0].equals("ID")){
				ID=pair[1];
				hasID=true;
			}else if(pair[0].equals("Derives_from")){
				Derives_from= new ArrayList<String>();
				for (String s : pair[1].split(",")) {
					Derives_from.add(s);
				}
				hasDerived_from=true;
			}else if(pair[0].equals("Name")){
				Name=pair[1];
				hasName=true;
			}
		}
	}

	
	
	public String getID() {
		return ID;
	}

	public String getName() {
		return Name;
	}

	public Iterator<String> iterateParents() {
		return Parent.iterator();
	}
	
	//Semigood solution
	public ArrayList<String> getParents(){
		return Parent;
	}
	
	public Iterator<String> iterateDerives_from(){
		return Derives_from.iterator();
	}

	public boolean hasParent() {
		return hasParent;
	}

	public boolean hasID() {
		return hasID;
	}
	
	public boolean hasDerives_from(){
		return hasDerived_from;
	}

	public boolean hasName() {
		return hasName;
	}

	public String toBed(){
		return this.getChr()+"\t"+this.getStart()+"\t"+this.getEnd()+"\t"+this.getFeature()+"\t"+this.getScore()+
		"\t"+this.getStrand();
	}
	
	public String toString(){
		return this.getChr()+"\t"+this.getSource()+"\t"+this.getFeature()+"\t"+(this.getStart()-1)+"\t"+
		this.getEnd()+"\t"+this.getScore()+"\t"+this.getStrand()+"\t"+this.getFrame()+"\t"+this.getAttribute();
	}
	
	public String getFlag() {
		return flag;
	}
	public void setFlag(String flag){
		this.flag=flag;
	}
	
	public String getChr() {
		return chr;
	}
	public void setChr(String chr) {
		this.chr = chr;
	}
	public String getSource() {
		return source;
	}
	public void setSource(String source) {
		this.source = source;
	}
	public String getFeature() {
		return feature;
	}
	public void setFeature(String feature) {
		this.feature = feature;
	}
	public String getScore() {
		return score;
	}
	public void setScore(String score) throws Exception{
		//check for correct values
		try {
			double test= Double.parseDouble(score);
			if(test>=-1000 && test<=1000){
				this.score = score;
			}else{
				throw new Exception("Value for score is out of range: "+ score);
			}
		} catch (Exception e) {
			if(score.equals(".")){
				this.score = score;
			}else{
				throw new Exception("Illegal value for score: "+score);
			}
		}
		
	}
	public String getAttribute() {
		return attribute;
	}
	public void setAttribute(String group) {
		this.attribute = group;
	}
	public int getStart() {
		return start;
	}
	public void setStart(int start) {
		this.start = start;
	}
	public int getEnd() {
		return end;
	}
	public void setEnd(int end) {
		this.end = end;
	}
	public char getFrame() {
		return frame;
	}
	public void setFrame(char frame) throws Exception{
		if(frame=='0'||frame=='1'||frame=='2'||frame=='.'){
			this.frame = frame;
		}else{
			throw new Exception("Illegal frame: "+frame);
		}
		
	}
	public char getStrand() {
		return strand;
	}
	public void setStrand(char strand) throws Exception{
		if(strand=='-'||strand=='+'||strand=='.'){
			this.strand = strand;
		}else{
			throw new Exception("Illegal strand: "+strand);
		}
	}

	public boolean overlap(gffLine gl2){
		if(!this.getChr().equals(gl2.getChr())){
			return false;
		}
		if(!((this.getStart()>=gl2.getStart()&&this.getStart()<gl2.getEnd())||(gl2.getStart()>=this.getStart()&&gl2.getStart()<this.getEnd()))){
			return false;
		}
		return true;
	}
	
	public boolean containsPos(int pos){
		return pos>=start && pos<=end;
	}
	
}
